<?php
namespace Model;

use Lib, Dao, Action;

class Queue {
	const QUEUE_TYPE_NORMAL = 1; // 正常获取当前 POSTFIX 队列。
	const QUEUE_TYPE_LIVE = 2; // 从缓存中获当当前 POSTFIX 队列中未发送的列表。
	const QUEUE_TYPE_ERROR = 3; // 从缓存中获当当前 POSTFIX 队列中已发送但失败的列表。
	private $dir = NULL;
	private $DLabel = NULL;
	private $DQueue = NULL;
	private $DWaiting = NULL;

	private function __construct() {
		$this->dir = \Core\HHO::$config['data.root'] . '/EDM';
		
		$this->DLabel = new Dao\Label();
		$this->DQueue = new Dao\Queue();
		$this->DWaiting = new Dao\Waiting();
	}

	public function getLabel() {
		$label = array();
		
		foreach ($this->DLabel->select() as $item) {
			$label[] = $item;
		}
		
		return $label;
	}

	/**
	 * 创建至待发队列
	 *
	 * - $label：队列标识符
	 * - $email：接收的邮箱列表
	 * - $sendtime：定时发送的时间，默认为即时发送
	 *
	 * @param int $label
	 * @param array $email
	 * @param int $sendtime
	 * @return void
	 */
	public function make($label, $email, $sendtime = 0) {
		$list = array();
		
		$sendtime || $sendtime = time();
		
		foreach ($email as $mail) {
			$list[] = array($label, $sendtime, $mail['name'], $mail['prefix'], $mail['host']);
			
			if (count($list) >= 1000) {
				$this->DWaiting->insert($list, array('label', 'sendtime', 'name', 'prefix', 'host'));
				
				$list = array();
			}
		}
		
		if (count($list) > 0) {
			$this->DWaiting->insert($list, array('label', 'sendtime', 'name', 'prefix', 'host'));
		}
	}

	/**	 * 从待发队列转至发送队列	 *	 * @param int $label
	 * @param int $limit	 * @param array $reject	 * @return int	 */
	public function activate($label, $limit, $reject = NULL) {
		$where = array('label' => $label, 'sendtime <= ' . time());
		
		if ($reject) {
			$x = array();
			
			foreach ($reject as $item) {
				$x[] = "'$item'";
			}
			
			$where[] = "host NOT IN (" . implode(',', $x) . ')';
		} else {
			$reject = array();
		}
		
		$queue = $this->DWaiting->select($where, array('sendtime', 'id'), $limit);
		if (count($queue) <= 0) {
			return 0;
		}
		
		// ==================================================================		$hosts = array();
		
		$min = $limit / 10;
		
		foreach ($queue as $item) {
			isset($hosts[$item['host']]) || $hosts[$item['host']] = 0;
			
			if ($hosts[$item['host']] > $min) {
				continue;
			}
			
			$this->DQueue->insert(array('label' => $label, 'sendtime' => $item['sendtime'], 'name' => $item['name'], 'prefix' => $item['prefix'], 'host' => $item['host'], 'marktime' => time()));
			$this->DWaiting->delete(array('id' => $item['id']));
			
			if (++$hosts[$item['host']] > $min) {
				$reject[] = $item['host'];
			}
		}
		
		// ==================================================================		$sum = array_sum($hosts);
		
		if ($sum < $limit) {
			$sum += $this->activate($label, $limit - $sum, $reject);
		}
		
		return $sum;
	}

	/**
	 * 统计队列数量
	 *
	 * @param int $label
	 * @param boolean $live
	 * @param string $host
	 * @param int $starttime
	 * @param int $endtime
	 * @param boolean $waiting
	 * @return int
	 */
	public function count($label = 0, $live = NULL, $host = '', $starttime = 0, $endtime = 0, $waiting = FALSE) {
		$where = [];
		
		$label && $where['label'] = $label;
		$host && $where['host'] = $host;
		
		$starttime > 0 && $where[] = 'sendtime >= ' . $starttime;
		$endtime > 0 && $where[] = 'sendtime <= ' . $endtime;
		
		if ($waiting) {
			$count = $this->DWaiting->select($where, NULL, NULL, 'count(*) as amount');
		} else {
			$live === TRUE && $where['live'] = 1;
			$live === FALSE && $where['live'] = 0;
			
			$count = $this->DQueue->select($where, NULL, NULL, 'count(*) as amount');
		}
		
		return intval($count[0]['amount']);
	}

	/**
	 * 获得各主机接收者数量
	 *
	 * @param int $label
	 * @return array
	 */
	public function hosts($label) {
		$list = array();
		
		$hosts = $this->DQueue->select(array('label' => $label, 'live' => 1), array('amount'), NULL, array('host', 'count(*) as amount'), array('host'));
		
		foreach ($hosts->fetch() as $item) {
			$list[$item['host']] = $item['amount'];
		}
		
		return $list;
	}

	/**	 * 获得指定条件的接收者	 *	 * @param int $label	 * @param boolean $live	 * @param string $host	 * @param int $starttime	 * @param int $endtime	 * @return int	 */
	public function get($label, $live = NULL, $host = '', $starttime = 0, $endtime = 0) {
		$where = array('label' => $label);
		
		$host && $where['host'] = $host;
		
		$live === TRUE && $where['live'] = 1;
		$live === FALSE && $where['live'] = 0;
		
		$starttime > 0 && $where[] = 'sendtime >= ' . $starttime;
		$endtime > 0 && $where[] = 'sendtime <= ' . $endtime;
		
		return $this->DQueue->select($where, array('id'))->fetch();
	}

	/**
	 * 从待发队列或发送队列中清除指定队列，或从发送队列中清除超时和已发送的接收者
	 *
	 * @param int $label
	 * @param int $outtime
	 * @param int $deltime
	 * @param boolean $waiting
	 * @return int
	 */
	public function clean($label, $outtime = 0, $deltime = 0, $waiting = FALSE) {
		$where = array('label' => $label);
		
		if ($waiting) {
			return $this->DWaiting->delete($where);
		}
		
		if ($outtime && $deltime) {
			$where[] = "(marktime <= $outtime OR live = 0 AND sendtime <= $deltime)";
		} elseif ($outtime) {
			$where[] = 'marktime <= ' . $outtime;
		} elseif ($deltime) {
			$where[] = 'live = 0 AND sendtime <= ' . $deltime;
		}
		
		return $this->DQueue->delete($where);
	}

	/**
	 * 设置指定主机或邮箱的发送时间，当 $sendtime 小于 2011-01-01 00:00:00 时，将执行推迟动作。
	 *
	 * @param int $label
	 * @param int $sendtime
	 * @param string $host
	 * @param string $email
	 * @return int
	 */
	public function settime($label, $sendtime, $host = NULL, $email = NULL) {
		$where = array('live' => 1);
		
		$label && $where['label'] = $label;
		$host && $where['host'] = $host;
		
		if (!$host && $email) {
			$mail = explode('@', $email);
			
			$where['host'] = $mail[1];
			$where['prefix'] = $mail[0];
		}
		
		return $this->DQueue->update('sendtime = ' . ($sendtime > 1293811200 ? $sendtime : "sendtime + $sendtime"), $where);
	}

	/**
	 * 设置指定邮箱的发送状态
	 *
	 * @param int $label
	 * @param boolean $live
	 * @param string $email
	 * @param string|array $host
	 * @return boolean
	 */
	public function setlive($label, $live, $email = NULL, $host = NULL) {
		$where = array('label' => $label);
		$host && $where['host'] = $host;
		
		if (!$host && $email) {
			$mail = explode('@', $email);
			
			$where['host'] = $mail[1];
			$where['prefix'] = $mail[0];
		}
		
		return $this->DQueue->update(array('live' => $live ? 1 : 0), $where);
	}

	/**
	 * 获得 SMTP 服务器
	 *
	 * @param int $label
	 * @param array $receive
	 * @return array
	 */
	public function mailer($label, $receive) {
		$mailer = Action\EDM::mailer($label);
		
		return $mailer[array_rand($mailer)];
	}

	/**
	 * 获得邮件的标题和正文
	 *
	 * @param int $label
	 * @param array $receive
	 * @return array
	 */
	public function message($label, $receive) {
		$message = Action\EDM::message($label);
		
		if (in_array($receive['host'], array('gmail.com', 'qq.com', 'foxmail.com', 'msn.com', 'hotmail.com', 'live.cn', 'live.com'))) {
			foreach ($message as $k => $item) {
				if ($item['mode'] == Action\EDM::EDM_MODE_IMG) {
					unset($message[$k]);
				}
			}
		} else {
			foreach ($message as $k => $item) {
				if ($item['mode'] == Action\EDM::EDM_MODE_NONE) {
					unset($message[$k]);
				}
			}
		}
		
		$message = $message[array_rand($message)];
		
		// ==================================================================
		$random = mt_rand(0, 200);
		
		if (in_array($random, array(10, 110))) {
			$message['title'] = 'Subject: ' . $message['title'] . '(' . date('Y-m-d') . ')';
		} elseif (in_array($random, array(30, 130))) {
			$message['title'] = 'FW: ' . $message['title'];
		} elseif (in_array($random, array(50, 150))) {
			$message['title'] = '转发: ' . $message['title'];
		} elseif (in_array($random, array(70, 170))) {
			$message['title'] = $message['title'] . '(' . date('Y-m-d') . ')';
		} elseif (in_array($random, array(20, 35, 60, 80, 100, 125, 160, 175, 190))) {
			$message['title'] = $message['title'] . '(AD)';
		}
		
		// ==================================================================
		$message['content'] = file_get_contents($this->dir . '/' . $message['content']);
		$message['content'] = str_replace('{$LabelID}', $label, $message['content']);
		$message['content'] = str_replace('{$EmailID}', date('YmdHis') . mt_rand(10, 99), $message['content']);
		
		return $message;
	}

	/**	 * 从发送队列中获取一个接收邮箱	 *	 * @param int $label	 * @return array	 */
	public function receive($label) {
		$limit = &Action\EDM::$limit;
		
		while (TRUE) {
			$receive = $this->DQueue->select(array('label' => $label, 'live' => 1, 'sendtime<=' . time()), array('id'), 1);
			
			if (!isset($receive[0])) {
				return array();
			}
			
			$timestamp = time();
			
			if (isset($limit[$receive[0]['host']])) {
				if ($this->count(NULL, FALSE, $receive[0]['host'], $timestamp - $timestamp % 60, $timestamp) >= $limit[$receive[0]['host']]) {
					if ($this->settime(NULL, $timestamp - $timestamp % 60 + 60, $receive[0]['host'])) {
						continue;
					}
				}
			}
			
			break;
		}
		
		return $receive[0];
	}

	/**
	 * 发送邮件
	 *
	 * @param array $mailer
	 * @param array $message
	 * @param array $receive
	 * @return boolean
	 */
	public function send($mailer, $message, $receive) {
		$Smtp = new Lib\Smtp($mailer[1], $mailer[2], $mailer[4], $mailer[5], 25, 5);
		
		$Smtp->message($message['title'], str_replace('{$Email}', urlencode($receive['prefix'] . '@' . $receive['host']), $message['content']));
		
		isset($receive['name']) || $receive['name'] = '';
		
		if (!$Smtp->send("{$receive['name']}<{$receive['prefix']}@{$receive['host']}>", "{$mailer[3]}<{$mailer[4]}>", $mailer[6])) {
			return FALSE;
		}
		
		$Smtp->quit();
		
		return TRUE;
	}

	public function engineMyISAM() {
		return $this->DQueue->engine(Dao\Queue::ENGINE_MYISAM) !== FALSE;
	}

	public function engineMemory() {
		return $this->DQueue->engine(Dao\Queue::ENGINE_MEMORY) !== FALSE;
	}

	public function mailq($type) {
		if ($type == self::QUEUE_TYPE_LIVE) {
			$files = \Core\HHO::$config['runtime.root'] . '/MailQLive.json';
		} elseif ($type == self::QUEUE_TYPE_ERROR) {
			$files = \Core\HHO::$config['runtime.root'] . '/MailQError.json';
		} else {
			$files = '';
		}
		
		if ($files && is_file($files)) {
			if (filemtime($files) + 1 <= time()) {
				if (!$this->mailq(self::QUEUE_TYPE_NORMAL)) {
					is_file($files) && unlink($files);
					
					return [];
				}
			}
			
			return json_decode(file_get_contents($files), TRUE) ?  : [];
		}
		
		// ================================================================		$data = explode("\n\n", trim(`mailq`));
		if (count($data) <= 1) {
			return [];
		}
		
		foreach ($data as &$item) {
			$item = explode("\n", $item);
		}
		
		unset($data[0][0]);
		$data[0] = array_values($data[0]);
		
		unset($data[count($data) - 1]);
		
		// ================================================================
		$queue = $liveCache = $errorCache = [];
		
		foreach ($data as $item) {
			if (!preg_match('/^(\S+)\s+(\d+)\s+(\S.+\S)\s+(\S+@\S+)/', trim($item[0]), $tmp)) {
				continue;
			} elseif (count($item) < 2) {
				continue;
			}
			
			$id = rtrim($tmp[1], '*');
			
			$queue[$id] = array('live' => substr($tmp[1], -1) == '*', 'size' => $tmp[2], 'time' => strtotime($tmp[3]), 'from' => $tmp[4]);
			
			if (isset($item[2])) {
				$queue[$id]['to'] = trim($item[2]);
				$queue[$id]['error'] = substr(trim($item[1]), 1, -1);
			} else {
				$queue[$id]['to'] = trim($item[1]);
				$queue[$id]['error'] = '';
			}
			
			if ($queue[$id]['live']) {
				$liveCache[$id] = $queue[$id];
			} else {
				$errorCache[$id] = $queue[$id];
			}
		}
		
		file_put_contents(\Core\HHO::$config['runtime.root'] . '/MailQLive.json', json_encode($liveCache));
		file_put_contents(\Core\HHO::$config['runtime.root'] . '/MailQError.json', json_encode($errorCache));
		
		return $queue;
	}

	public static function getInstance() {
		static $instance = null;
		
		if (!$instance) {
			$instance = new self();
		}
		
		return $instance;
	}
}